package com.hmall.service.impl;

import com.alibaba.fastjson.JSON;
import com.hmall.dto.PageDTO;
import com.hmall.pojo.ItemDOC;
import com.hmall.service.SearchService;
import com.hmall.web.request.SearchParams;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;

/*
 *@author: RickChen
 *@description: 搜索服务实现类
 *@Time: 2023/4/9  14:44
 */
@Service
public class SearchServiceImpl implements SearchService {

    @Resource
    private RestHighLevelClient restHighLevelClient;
    /**
     * 过滤项聚合
     * @param searchParams
     * @return
     */
    @Override
    public Map<String, List<String>> fileters(SearchParams searchParams) {
        try {
            // 1.创建请求对象
            SearchRequest searchRequest = new SearchRequest("hmall");
            Map<String, List<String>> map=new HashMap<>();

            // 2.准备DSL
            // 2.1 限制聚合查询
            getBoolSearch(searchParams);
            // 2.2 聚合查询
            searchRequest.source().aggregation(AggregationBuilders.terms("brand_agg").field("brand").size(10));
            searchRequest.source().aggregation(AggregationBuilders.terms("category_agg").field("category").size(10));
            // 3. 发起查询
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //4. 解析响应
            map.put("category", getaggList(response, "category_agg"));
            map.put("brand", getaggList(response, "brand_agg"));

            return map;
        } catch (IOException e) {
            e.printStackTrace();
            throw  new RuntimeException("系统繁忙");
        }
    }

    /**
     * 通过条件生成复合查询对象
     */
    private QueryBuilder getBoolSearch(SearchParams params) {
        // 1.创建bool聚合请求对象
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 2.根据拥有条件进行bool条件追加:
        if (Objects.nonNull(params.getKey()) && !StringUtils.isBlank(params.getKey())) {
            queryBuilder.must(QueryBuilders.matchQuery("all", params.getKey()));
        }

        if (Objects.nonNull(params.getBrand()) && !StringUtils.isBlank(params.getBrand())) {
            queryBuilder.must(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        if (Objects.nonNull(params.getCategory()) && !StringUtils.isBlank(params.getCategory())) {
            queryBuilder.must(QueryBuilders.termQuery("category", params.getCategory()));
        }
        if (Objects.nonNull(params.getMaxPrice()) && Objects.nonNull(params.getMaxPrice())) {
            queryBuilder.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }

        return queryBuilder;
    }

    /**
     * 通过响应及其key获取聚合集合
     *
     * @param search
     * @param key
     * @return
     */
    private List<String> getaggList(SearchResponse search, String key) {
        Aggregations aggregations = search.getAggregations();
        Terms brandAgg = aggregations.get(key);
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        List<String> list = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            String asString = bucket.getKeyAsString();
            list.add(asString);
        }
        return list;
    }

    /**
     * 自动补全
     * @param key
     * @return
     */
    @Override
    public List<String> suggestion(String key) {
        try {
            // 创建查询请求对象
            SearchRequest searchRequest = new SearchRequest("hmall");

            // 设置补全查询语法
            searchRequest.source().suggest(new SuggestBuilder().addSuggestion(
                    // 设置补全名为MySuggestion 补全字段为suggestion
                    "mySuggestion", SuggestBuilders.completionSuggestion("suggest")
                            // 需补全的key
                            .prefix(key)
                            // 是否跳过重复项
                            .skipDuplicates(true)
                            // 最终显示补全数据条数
                            .size(10)
            ));

            // 发起查询 并获取响应数据
            SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            Suggest suggest = search.getSuggest();
            // 通过补全名称获取响应中的补全属性部分
            CompletionSuggestion completionSuggestion = suggest.getSuggestion("mySuggestion");
            // 解析数据
            // 获取Option部分
            List<CompletionSuggestion.Entry.Option> options = completionSuggestion.getOptions();
            // 定义集合存储解析数据
            List<String> list = new ArrayList<>(options.size());
            // 遍历解析
            for (CompletionSuggestion.Entry.Option option : options) {
                list.add(option.getText().string());
            }

            return list;
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    @Override
    public PageDTO<ItemDOC> list(SearchParams searchParams) {

        PageDTO<ItemDOC> pageDTO = new PageDTO<>();
        try {
            // 1. 创建请求对象
            SearchRequest searchRequest = new SearchRequest("hmall");

            // 2. 准备DSL查询语句
            QueryBuilder queryBuilder = getBoolSearch(searchParams);


            // 3. 发出请求并分页  接收响应
            // 3.1 准备分页数据
            Integer size = searchParams.getSize();
            int page = (searchParams.getPage() + 1) * size;
            //3.2 传入查询DSL及其分页数据及其价格排序
            SearchSourceBuilder sourceBuilder = searchRequest.source().query(queryBuilder).from(page).size(size);
            // 高亮处理
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("name").requireFieldMatch(false);
            //  .preTags("<em>").postTags("</em>")
            sourceBuilder.highlighter(highlightBuilder);
            // 排序方式处理
            if (!Objects.equals(searchParams.getSortBy(), "default")) {
                if (Objects.equals(searchParams.getSortBy(), "price")) {
                    sourceBuilder.sort("price", SortOrder.ASC);
                }
                if (Objects.equals(searchParams.getSortBy(), "sold")) {
                    sourceBuilder.sort("sold", SortOrder.DESC);
                }
                if (Objects.equals(searchParams.getCategory(), "comment_count")) {
                    sourceBuilder.sort("comment_count", SortOrder.DESC);
                }
            }

            // 3.3 执行
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //4. 封装数据
             pageDTO = response2Page(searchResponse);

        } catch (IOException e) {
            e.printStackTrace();
            return new PageDTO<>();
        }

        return pageDTO;


    }


    private PageDTO<ItemDOC> response2Page(SearchResponse searchResponse) {
        PageDTO<ItemDOC> pageDTO = new PageDTO<>();

        // 从响应获取hits对象
        SearchHits hits = searchResponse.getHits();
        // 获取查询总数
        long counts = hits.getTotalHits().value;
        // 从hits对象中获取hits数组对
        SearchHit[] hitsHits = hits.getHits();
        // 定义集合接收后续需要的对象
        List<ItemDOC> itemDOCS = new ArrayList<>();
        // 遍历并获取所需参数
        for (SearchHit hitsHit : hitsHits) {
            // JSON转换所需对象
            ItemDOC itemDOC = JSON.parseObject(hitsHit.getSourceAsString(), ItemDOC.class);
            // 获取每一个对象的高亮属性进行判别
            Map<String, HighlightField> highlightFields = hitsHit.getHighlightFields();
            // 判断是否存在高亮及其是否包含所需key
            if (Objects.nonNull(highlightFields) && highlightFields.containsKey("key")) {
                // 获取对应key的高亮数组，并获取高亮结果值
                String name = highlightFields.get("key").getFragments()[0].string();
                // 将原属性值替换为高亮属性值
                itemDOC.setName(name);
            }
            // 添加进入集合
            itemDOCS.add(itemDOC);
        }
        pageDTO.setList(itemDOCS);
        pageDTO.setTotal(counts);

        return pageDTO;
    }
}
